/*
	Copyright (C) 2011 Salil Bhagurkar

	This file is part of illusion

	illusion is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as
	published by the Free Software Foundation, either version 3 of
	the License, or (at your option) any later version.

	illusion is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with illusion. 
	If not, see <http://www.gnu.org/licenses/>.
*/

#include <klib/lib.h>
#include <apimod/apimod.h>
#include <kernel/list.h>
#include <kernel/errors.h>

#include "ishell.h"

extern struct apimod_console *console;
extern struct apimod_base *base;
extern struct apimod_lib_string *lib_string;
extern struct apimod_lib_memory *memory;


#define PTYPE_SYM 0 //A symbol which might be a variable or something to execute
#define PTYPE_OP 1



struct pobject {
	unsigned char type;
	char symbol[SYM_NAME_LEN];
	struct pobject *next, *prev;
};


static struct pobject *pobjects = null, *pobjectstail = null;

static struct pobject *result_pobjects = null, *result_pobjectstail = null;



#define POBJECT_STACK_SZ 20

//pobject stack

struct pobject_stack {
	unsigned long pobject_stack_ptr;
	struct pobject *pobject_stack[POBJECT_STACK_SZ];
};

static struct pobject_stack stack = {.pobject_stack_ptr = 0}, opstack = {.pobject_stack_ptr = 0};

static int push_pobject(struct pobject_stack *stack, struct pobject *p)
{
	if((stack->pobject_stack_ptr) < POBJECT_STACK_SZ)
		stack->pobject_stack[(stack->pobject_stack_ptr)++] = p;
	else {
		console->printf("ishell: pobject stack full\n");
		return ERESRC;
	}
	return 0;
}


static struct pobject *pop_pobject(struct pobject_stack *stack)
{
	struct pobject *ret;
	if((stack->pobject_stack_ptr) > 0) {
		ret = stack->pobject_stack[--(stack->pobject_stack_ptr)];
		return ret;
	}
	return null;
}

static struct pobject *stack_base(struct pobject_stack *stack)
{
	return stack->pobject_stack[stack->pobject_stack_ptr - 1];
}

static void attach_pobject(struct pobject *p)
{
	list_attach(pobjects, pobjectstail, p);
}

static struct pobject *new_pobject(unsigned long type, char *symbol)
{
	struct pobject *n;
	n = (struct pobject *)memory->malloc(sizeof(struct pobject));
	n->prev = n->next = null;
	n->type = type;
	lib_string->copy(n->symbol, symbol, SYM_NAME_LEN);
	return n;
}

static void new_pobject_add(unsigned long type, char *symbol)
{
	struct pobject *p = new_pobject(type, symbol);
	list_attach(pobjects, pobjectstail, p);
}

static int is_operation(char x)
{
	return ( (x == '+') || ( x == '-') || ( x == '*') || ( x == '/')
			|| (x == '(') || (x == ')') || (x == '=') || (x == '<')
			|| (x == '>') || (x == '&') || (x == '|') || (x == '%')
			|| (x == '[') || (x == ']'));
}


static int is_whitespace(char x)
{
	return ( (x == ' ') || (x == '\t') || (x == '\r') || (x == '\n'));
}


int precedence(char *x)
{
	if(x[0] == '(' || x[0] == ')' || x[0] == ']' || x[0] == '[')
		return 19;
	if(x[0] == '*' || x[0] == '/' || x[0] == '%')
		return 18;
	if(x[0] == '+' || x[0] == '-')
		return 17;
	return 0;
}



static struct pobject *run_instruction(struct pobject *op, struct pobject *p0, struct pobject *p1)
{
	console->printf("%s ", op->symbol);
	console->printf("%s ", p0->symbol);
	console->printf("%s ", p1->symbol);
	console->printf("\n");

	struct pobject *p = new_pobject(PTYPE_SYM, "result");
	list_attach(result_pobjects, result_pobjectstail, p);
	int err = shell_op(p->symbol, op->symbol, p0->symbol, p1->symbol);
	if(err) {
		memory->free(p, sizeof(struct pobject));
		return null;
	}
	return p;
}

static unsigned scan_op(char *sym, char *exp)
{
	//Scan two character operations
	if(exp[0] == '<' || exp[0] == '>') {
		if(exp[1] == '=') {
			sym[2] = '\0';
			sym[0] = exp[0];
			sym[1] = exp[1];
			new_pobject_add(PTYPE_OP, sym);
			return 2;
		}
	} else if(exp[0] == '&') {
		if(exp[1] == '&') {
			sym[2] = '\0';
			sym[0] = exp[0];
			sym[1] = exp[1];
			new_pobject_add(PTYPE_OP, sym);
			return 2;
		}
	} else if(exp[0] == '|') {
		if(exp[1] == '|') {
			sym[2] = '\0';
			sym[0] = exp[0];
			sym[1] = exp[1];
			new_pobject_add(PTYPE_OP, sym);
			return 2;
		}
	}
	sym[1] = '\0';
	sym[0] = exp[0];
	new_pobject_add(PTYPE_OP, sym);
	return 1;
}

static void pobjectize(char *exp)
{
	unsigned long i, j, slen;
	char sym[SYM_NAME_LEN];
	slen = lib_string->length(exp);
	for(i=0; i<slen;) {
		//Check if it is an operation
		if(is_operation(exp[i])) {
			char sym[3];
			i += scan_op(sym, exp + i);
		} else {
			char non_zero = 0;
			//Scan a variable / constant / execute
			j = 0;

			if(exp[i] == '"') {
				//Skip over the "
				i++;
				while(exp[i] != '"' && i < slen && j < (SYM_NAME_LEN - 1)) {
					sym[j++] = exp[i++];
					non_zero = 1;
				}
				if(exp[i] != '"') {
					sym[j] = '\0';
					console->printf("ishell: String incomplete: %s\n", sym);
					return;
				}
				//Skip over the ending "
				i++;
			} else if(exp[i] == '`') {
				i++;
				sym[j++] = '`';
				while(exp[i] != '`' && i < slen && j < (SYM_NAME_LEN - 2)) {
					sym[j++] = exp[i++];
					non_zero = 1;
				}
				if(exp[i] != '`') {
					sym[j] = '\0';
					console->printf("ishell: Execute string incomplete: %s\n", sym);
					return;
				}
				sym[j++] = '`';
				i++;
			} else {
				while(!(is_whitespace(exp[i])) && i < slen && j < (SYM_NAME_LEN - 1)
						&& !is_operation(exp[i])) {
					sym[j++] = exp[i++];
					non_zero = 1;
				}
			}

			if(non_zero) {
				sym[j] = 0;
				new_pobject_add(PTYPE_SYM, sym);
			} else {
				i++;
			}
		}
	}
}

static void do_parse()
{
	//Now do the token parsing
	unsigned long pos = 0;
	struct pobject *t;
	list_for(pobjects, t) {
		if(t->type == PTYPE_SYM) {
			push_pobject(&stack, t);
		} else {
			if(t->symbol[0] == ')') {
				while((opstack.pobject_stack_ptr) > 0) {
					struct pobject *op, *p0, *p1;
					op = pop_pobject(&opstack);
					if(op->symbol[0] == '(')
						break;
					p0 = pop_pobject(&stack);
					if(!p0) {
						console->printf("ishell: Parse failure\n");
						return;
					}
					p1 = pop_pobject(&stack);
					if(!p1) {
						console->printf("ishell: Parse failure\n");
						return;
					}

					struct pobject *result = run_instruction(op, p0, p1);
					if(!result) {
						console->printf("ishell: Instruction failure: %s %s %s\n", p0->symbol,
								op->symbol, p1->symbol);
						return;
					}
					push_pobject(&stack, result);
				}
			} else {
				if(opstack.pobject_stack_ptr==0 ||
		stack_base(&opstack)->symbol[0] == '(' ||
		(precedence(t->symbol) > precedence(stack_base(&opstack)->symbol))) {
					push_pobject(&opstack, t);
				} else {
					struct pobject *op, *p0, *p1;
					op = pop_pobject(&opstack);
					if(!op) {
						console->printf("ishell: Parse failure\n");
						return;
					}
					p0 = pop_pobject(&stack);
					if(!p0) {
						console->printf("ishell: Parse failure\n");
						return;
					}
					p1 = pop_pobject(&stack);
					if(!p1) {
						console->printf("ishell: Parse failure\n");
						return;
					}

					struct pobject *result = run_instruction(op, p0, p1);
					if(!result) {
						console->printf("ishell: Instruction failure: %s %s %s\n", p0->symbol,
								op->symbol, p1->symbol);
						return;
					}
					push_pobject(&stack, result);

					push_pobject(&opstack, t);
				}
			}
		}
	}
	while(opstack.pobject_stack_ptr > 0) {
		struct pobject *op, *p0, *p1;
		op = pop_pobject(&opstack);
		if(!op) {
			console->printf("ishell: Parse failure\n");
			return;
		}
		p0 = pop_pobject(&stack);
		if(!p0) {
			console->printf("ishell: Parse failure\n");
			return;
		}
		p1 = pop_pobject(&stack);
		if(!p1) {
			console->printf("ishell: Parse failure\n");
			return;
		}

		struct pobject *result = run_instruction(op, p0, p1);
		if(!result) {
			console->printf("ishell: Instruction failure: %s %s %s\n", p0->symbol,
					op->symbol, p1->symbol);
			return;
		}
		push_pobject(&stack, result);
	}
}

#define NUM_LEN 20

extern char cmdbuf[];

void parse_expression()
{
	pobjectize(cmdbuf);
	struct pobject *pobject;

	uint_t eq_count = 0, first_pos = 0, i = 0;
	list_for(pobjects, pobject) {
		if(pobject->symbol[0] == '=' && pobject->symbol[1] == '\0') {
			if(!eq_count)
				first_pos = i;
			eq_count++;
		}
		i++;
	}
	if(eq_count == 1 && first_pos == 1)
		do_parse();
	else if(!eq_count)
		shell_exec();
	else {
		console->printf("ishell: Invalid \"=\" token count = %u, position = %u\n", eq_count, first_pos);
	}

	//Pop off all from stack
	while(stack.pobject_stack_ptr > 0) {
		pop_pobject(&stack);
	}

	for(pobject = pobjects; pobject;) {
		struct pobject *next = pobject->next;
		memory->free(pobject, sizeof(struct pobject));
		pobject = next;
	}
	for(pobject = result_pobjects; pobject;) {
		struct pobject *next = pobject->next;
		memory->free(pobject, sizeof(struct pobject));
		pobject = next;
	}
}
